package slap.concurrent;

import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import slap.exception.SlapException;
import slap.http.Request;
import slap.util.Time;

public abstract class Job<V> extends Invoker.Invocation implements Callable<V> {
	protected static final Logger logger = LoggerFactory.getLogger(Job.class);
	protected long lastRun = 0;
	protected boolean wasError = false;
	protected Throwable lastException = null;
	
	public Future future;
	
	/**
	 * Here you do the job
	 */
	public void doJob() throws Exception {
	}

	/**
	 * Here you do the job and return a result
	 */
	public V doJobWithResult() throws Exception {
		doJob();
		return null;
	}

	@Override
	public void execute() throws Exception {
	}

	/**
	 * Start this job now (well ASAP)
	 * 
	 * @return the job completion
	 */
	public Promise<V> now() {
		final Promise<V> smartFuture = new Promise<V>();
		Invoker.executor.submit(new Callable<V>() {
			public V call() throws Exception {
				V result = Job.this.call();
				smartFuture.invoke(result);
				return result;
			}

		});

		return smartFuture;
	}

	/**
	 * Start this job in several seconds
	 * 
	 * @return the job completion
	 */
	public Promise<V> in(String delay) {
		return in(Time.parseDuration(delay));
	}

	/**
	 * Start this job in several seconds
	 * 
	 * @return the job completion
	 */
	public Promise<V> in(int seconds) {
		final Promise<V> smartFuture = new Promise<V>();

		Invoker.executor.schedule(new Callable<V>() {

			public V call() throws Exception {
				V result = Job.this.call();
				smartFuture.invoke(result);
				return result;
			}

		}, seconds, TimeUnit.SECONDS);

		return smartFuture;
	}

	/**
	 * Run this job every n seconds
	 */
	public void every(String delay) {
		every(Time.parseDuration(delay));
	}

	/**
	 * Run this job every n seconds
	 */
	public void every(int seconds) {
		this.future =  Invoker.executor.scheduleWithFixedDelay(this, seconds, seconds,
				TimeUnit.SECONDS);
	}
	
	// Customize Invocation
	@Override
	public void onException(Throwable e) {
		wasError = true;
		lastException = e;
		try {
			super.onException(e);
		} catch (Throwable ex) {
			logger.error("Error during job execution ({})", this, ex);
		}
	}

	@Override
	public void run() {
		call();
	}

	public V call() {
		try {
			if (init()) {
				before();
				V result = null;

				try {
					lastException = null;
					lastRun = System.currentTimeMillis();

					result = doJobWithResult();
					wasError = false;
				} catch (SlapException e) {
					throw e;
				} catch (Exception e) {
					e.printStackTrace();
				}
				after();
				return result;
			}
		} catch (Throwable e) {
			onException(e);
		} finally {
			_finally();
		}
		return null;
	}

	@Override
	public void _finally() {
		super._finally();
	}

	@Override
	public String toString() {
		return this.getClass().getName();
	}

}
